#include <rid.h>
#include <stddef.h>

#include <unfy.h>

#define ARRAY_UNITS(array) (sizeof(array) / sizeof(*array))

typedef struct {
	Rid var_ids[8];
	int id_cnt;
	void *up;
} Binds_part;

static int
part_holds(const Binds_part *part, const Rid id)
{
	for (; part; part = part->up)
		for (int i = 0; i < part->id_cnt; ++i)
			if (!rid_cmp(id, part->var_ids[i]))
			        return 1;

	return 0;
}

/* uses more of stack over allocating memory, might want to rethink */
static size_t
max_binds_list(const Unfy_list *list, void *up)
{

	size_t len = 0;
	Binds_part part = { .up = up, .id_cnt = 0 };

	for (; list; list = list->next) {
		const Unfy_term *term = list->term;
		Binds_part *ptr = &part;
		size_t sub_len;

		switch (term->type) {
		case UNFY_CONST:
			break;
		case UNFY_LIST:
			sub_len = max_binds_list(term->u.list, &part);

			if (sub_len < 0)
				return -1;

			len += sub_len;
			break;
		case UNFY_VAR:
			if (part_holds(&part, term->u.id))
				break;

			++len;

			if (part.id_cnt < ARRAY_UNITS(part.var_ids)) {
				rid_set(part.var_ids[part.id_cnt], term->u.id);
				++part.id_cnt;
				break;
			}

			if ((sub_len = max_binds_list(list->next, &part)) < 0)
				return -1;

			return sub_len + len;
		}
	}
}

static size_t
max_binds(const Unfy_term *term)
{
	switch (term->type) {
	case UNFY_CONST:
	        return 0;
	case UNFY_VAR:
	        return 1;
	case UNFY_LIST:
	        return max_binds_list(term->u.list, NULL);
	}

	/* this will never happen */
	return -1;
}

static size_t
bind_len(const Unfy_bind *bind)
{
	size_t len = 0;

	for (; bind; bind = bind->next, ++len);

	return len;
}

static void
rec_binds(Unfy_recycle *rec, Unfy_bind *lbind, Unfy_bind *rbind)
{
	if (lbind)
		unfy_recycle_bind(rec, lbind);

	if (rbind)
		unfy_recycle_bind(rec, rbind);
}

int
aha_list_add(Unfy_list **list, Unfy_term *term, Unfy_recycle *rec)
{
	size_t lmax = max_binds(term);
	Unfy_list *here;

	if (lmax < 0)
		return -1;

	for (; *list; list = &(*list)->next) {
		const Unfy_term *rterm = (*list)->term;
		Unfy_bind *lbind = NULL;
		Unfy_bind *rbind = NULL;
		size_t rmax;

		switch (unfy_unify(term, rterm, &lbind, &rbind, rec)) {
		case UNFY_YES:
			if (bind_len(lbind) <= bind_len(rbind)) {
				rec_binds(rec, lbind, rbind);
				goto exit;
			}

			rec_binds(rec, lbind, rbind);
			break;
		case UNFY_NO:
			rec_binds(rec, lbind, rbind);

			if ((rmax = max_binds(rterm)) < 0)
				return -1;

			if (lmax < rmax)
				goto exit;

			break;
		case UNFY_ERR:
			rec_binds(rec, lbind, rbind);
			return -1;
		}
	}
exit:
	if (!(here = unfy_list(term, *list, rec)))
		return -1;

	*list = here;
	return 0;
}

Unfy_stat
aha_list_query(Unfy_list **list, const Unfy_term *term,
	       Unfy_bind **lbind, Unfy_bind **rbind, Unfy_recycle *rec)
{
	Unfy_stat stat = UNFY_NO;

	for (; *list; *list = (*list)->next) {
		const Unfy_term *rterm = (*list)->term;

		switch (unfy_unify(term, rterm, lbind, rbind, rec)) {
		case UNFY_YES:
			stat = UNFY_YES;
			goto exit;
		case UNFY_ERR:
		        stat = UNFY_ERR;
			/* fallthru */
		case UNFY_NO:
			rec_binds(rec, *lbind, *rbind);
		        goto exit;
		}
	}
exit:
	return stat;
}

